package org.dding.msc.service.impl;

import static java.lang.Thread.holdsLock;

import java.io.IOException;
import java.io.Writer;
import java.security.AccessController;
import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;

import org.dding.core.log.ILogger;
import org.dding.core.log.LoggerFactory;
import org.dding.msc.exception.ServiceLifecycException;
import org.dding.msc.exception.ServiceNotFoundException;
import org.dding.msc.exception.StartException;
import org.dding.msc.exception.StopException;
import org.dding.msc.exception.WaitingException;
import org.dding.msc.service.Command;
import org.dding.msc.service.DependencyInfo;
import org.dding.msc.service.Service;
import org.dding.msc.service.ServiceContainer;
import org.dding.msc.service.ServiceController;
import org.dding.msc.service.ServiceListener;
import org.dding.msc.service.ServiceName;
import org.dding.msc.service.ServiceRegistration;
import org.dding.msc.service.ServiceState;
import org.dding.msc.service.ServiceStateInfo;
import org.dding.msc.service.ServiceStateManager;
import org.dding.msc.service.SetTCCLAction;
import org.dding.msc.service.StartContext;
import org.dding.msc.service.StopContext;
import org.dding.msc.service.management.ServiceStatus;
import org.dding.msc.value.Value;

/**
 * The service controller implementation.
 * 
 * @param <S>
 *            the service type
 * 
 */
final class ServiceControllerImpl<S> implements ServiceController<S> {

	private ILogger logger = LoggerFactory.getLogger(getClass());

	private static final String ILLEGAL_CONTROLLER_STATE = "Illegal controller state";

	private ServiceStateManager stateManager = ServiceStateManager
			.getInstance();
	/**
	 * The service itself.
	 */
	private final Value<? extends Service<S>> serviceValue;
	/**
	 * The set of registered service listeners.
	 */
	private final IdentityHashMap<ServiceListener<? super S>, ServiceListener.Inheritance> listeners;
	/**
	 * The primary registration of this service.
	 */
	private final ServiceRegistration primaryRegistration;
	/**
	 * The alias registrations of this service.
	 */
	private final ServiceRegistration[] aliasRegistrations;
	/**
	 * The start exception.
	 */
	private ServiceLifecycException serviceLifecycException;
	/**
	 * The controller mode.
	 */
	private ServiceController.Mode mode = ServiceController.Mode.NEVER;
	/**
	 * The controller state.
	 */
	private State state = State.NEW;

	/**
	 * The number of asynchronous tasks that are currently running. This
	 * includes listeners, start/stop methods, outstanding asynchronous
	 * start/stops, and internal tasks.
	 */
	private int asyncTasks;
	/**
	 * The system nanotime of the moment in which the last lifecycle change was
	 * initiated.
	 */
	private volatile long lifecycleTime;

	private Command command = new Command(Transition.ERROR);

	private static final String[] NO_STRINGS = new String[0];

	ServiceControllerImpl(
			final Value<? extends Service<S>> serviceValue,
			final ServiceRegistration primaryRegistration,
			final ServiceRegistration[] aliasRegistrations,
			final Map<? extends ServiceListener<? super S>, ServiceListener.Inheritance> listeners) {
		this.serviceValue = serviceValue;
		this.primaryRegistration = primaryRegistration;
		this.aliasRegistrations = aliasRegistrations;
		this.listeners = new IdentityHashMap<ServiceListener<? super S>, ServiceListener.Inheritance>(
				listeners);
	}

	void addAsyncTasks(final int size) {
		asyncTasks += size;
	}

	void removeAsyncTask() {
		asyncTasks--;
	}

	void removeAsyncTasks(final int size) {
		asyncTasks -= size;
	}

	/**
	 * Start this service installation, connecting it to its parent and
	 * dependencies. Also, set the instance in primary and alias registrations.
	 * <p>
	 * All notifications from dependencies, parents, and registrations will be
	 * ignored until the installation is
	 * {@link #commitInstallation(org.jboss.msc.service.ServiceController.Mode)
	 * committed}.
	 */
	void startInstallation() {
		// Install the controller in each registration
		primaryRegistration.setInstance(this);

		for (ServiceRegistration aliasRegistration : aliasRegistrations) {
			aliasRegistration.setInstance(this);
		}
	}

	/**
	 * Commit the service install, kicking off the mode set and listener
	 * execution.
	 * 
	 * @param initialMode
	 *            the initial service mode
	 */
	void commitInstallation(Mode initialMode) {
		assert (state == State.NEW);
		assert initialMode != null;
		assert !holdsLock(this);
		final ArrayList<Runnable> listenerAddedTasks = new ArrayList<Runnable>(
				16);
		final ArrayList<Runnable> tasks = new ArrayList<Runnable>(16);

		synchronized (this) {
			getListenerTasks(ListenerNotification.LISTENER_ADDED,
					listenerAddedTasks);
			internalSetMode(initialMode, tasks);
			// placeholder async task for running listener added tasks
			asyncTasks += listenerAddedTasks.size() + tasks.size() + 1;
		}
		doExecute(tasks);
		tasks.clear();
		for (Runnable listenerAddedTask : listenerAddedTasks) {
			listenerAddedTask.run();
		}
		synchronized (this) {
			state = State.NEW;
			// subtract one to compensate for +1 above
			asyncTasks--;
			transition(tasks);
			asyncTasks += tasks.size();
		}
		doExecute(tasks);
	}

	/**
	 * Roll back the service install.
	 */
	void rollbackInstallation() {
		synchronized (this) {
			mode = Mode.REMOVE;
			asyncTasks++;
			state = State.DOWN;
		}
		(new RemoveTask()).run();
	}

	/**
	 * Return {@code true} only if this service controller installation is
	 * committed.
	 * 
	 * @return true if this service controller installation is committed
	 */
	boolean isInstallationCommitted() {
		assert holdsLock(this);
		return state.compareTo(State.DOWN) > 0;
	}

	/**
	 * Identify the transition to take. Call under lock.
	 * 
	 * @return the transition or {@code null} if none is needed at this time
	 */
	private Transition getTransition() {
		assert holdsLock(this);
		switch (state) {
		case NEW: {
			if (mode != Mode.ON_DEMAND && mode != Mode.REMOVE) {
				return Transition.NEW_to_STARTING;
			}
		}
			break;
		case DOWN: {
			if (mode == Mode.REMOVE
					|| Transition.DOWN_to_REMOVE.equals(command.getName())) {
				return Transition.DOWN_to_REMOVE;
			}
			break;
		}
		case WAITING: {
			if (mode != Mode.ON_DEMAND
					&& Transition.WAITING_to_STOPPING.equals(command.getName())) {
				return Transition.WAITING_to_STOPPING;
			} else if (mode != Mode.ON_DEMAND
					&& Transition.WAITING_to_UP.equals(command.getName())) {
				return Transition.WAITING_to_UP;
			}
			break;
		}
		case STOPPING: {
			if (serviceLifecycException == null) {
				return Transition.STOPPING_to_DOWN;
			} else {
				return Transition.STOPPING_to_PROBLEM;
			}
		}
		case UP: {
			if (Transition.UP_to_WAITING.equals(command.getName())) {
				return Transition.UP_to_WAITING;
			} else if (Transition.UP_to_STOPPING.equals(command.getName())) {
				return Transition.UP_to_STOPPING;
			}
			break;
		}
		case STARTING: {
			if (serviceLifecycException == null) {
				return Transition.STARTING_to_UP;
			} else {
				return Transition.STARTING_to_PROBLEM;
			}
		}
		case PROBLEM: {
			if (Transition.PROBLEM_to_DOWN.equals(command.getName())) {
				return Transition.PROBLEM_to_DOWN;
			} else if (Transition.PROBLEM_to_STARTING.equals(command.getName())) {
				return Transition.PROBLEM_to_STARTING;
			} else if (Transition.PROBLEM_to_STOPPING.equals(command.getName())) {
				serviceLifecycException = null;
				return Transition.PROBLEM_to_STOPPING;
			}
		}
		}
		return null;
	}

	/**
	 * Run the locked portion of a transition. Call under lock.
	 * 
	 * @param tasks
	 *            the list to which async tasks should be appended
	 */
	void transition(final ArrayList<Runnable> tasks) {
		assert holdsLock(this);
		Transition transition = null;
		do {
			if (asyncTasks != 0) {
				// no movement possible
				return;
			}
			transition = getTransition();
			if (transition == null) {
				return;
			}
			switch (transition) {
			case NEW_to_STARTING: {
				getListenerTasks(transition, tasks);
				tasks.add(new StartTask());
				break;
			}
			case STARTING_to_UP: {
				getListenerTasks(transition, tasks);
				break;
			}
			case UP_to_STOPPING: {
				getListenerTasks(transition, tasks);
				serviceLifecycException = null;
				tasks.add(new StopTask());
				break;
			}
			case STOPPING_to_DOWN: {
				getListenerTasks(transition, tasks);
				break;
			}
			case UP_to_WAITING: {
				getListenerTasks(transition, tasks);
				handlerState();
				tasks.add(new ServiceUnavailableTask());
				break;
			}
			case WAITING_to_UP: {
				getListenerTasks(transition, tasks);
				tasks.add(new ServiceAvailableTask());
				break;
			}
			case PROBLEM_to_DOWN: {
				getListenerTasks(transition, tasks);
				listeners.clear();
				break;
			}
			case PROBLEM_to_STARTING: {
				getListenerTasks(transition, tasks);
				tasks.add(new StartTask());
				break;
			}
			case WAITING_to_STOPPING: {
				getListenerTasks(transition, tasks);
				break;
			}
			case STARTING_to_PROBLEM:
			case STOPPING_to_PROBLEM: {
				getListenerTasks(transition, tasks);
				handlerState();
				break;
			}
			case PROBLEM_to_STOPPING: {
				getListenerTasks(transition, tasks);
				break;
			}
			case DOWN_to_REMOVE: {
				getListenerTasks(transition, tasks);
				listeners.clear();
				break;
			}
			default: {
				throw new IllegalStateException();
			}
			}
			state = transition.getAfter();
		} while (tasks.isEmpty());
	}

	public void setState(State targetState) throws ServiceLifecycException {
		State currentState = getState();
		Transition transition = toTransition(currentState, targetState);
		command = new Command(transition);
		final ArrayList<Runnable> tasks = new ArrayList<Runnable>(16);
		synchronized (this) {
			transition(tasks);
			asyncTasks += tasks.size();
		}
		doExecute(tasks);
	}

	private Transition toTransition(State currentState, State targetState)
			throws ServiceLifecycException {
		Transition transition = Transition.mapping(currentState, targetState);
		if (transition == Transition.ERROR)
			throw new ServiceLifecycException("service can't transfor form "
					+ currentState.getState() + " to " + targetState.getState());
		return transition;
	}

	private void getListenerTasks(final Transition transition,
			final ArrayList<Runnable> tasks) {
		final IdentityHashMap<ServiceListener<? super S>, ServiceListener.Inheritance> listeners = this.listeners;
		for (ServiceListener<? super S> listener : listeners.keySet()) {
			tasks.add(new ListenerTask(listener, transition));
		}
	}

	private void handlerState() {
		ServiceState state = stateManager.getServiceState(getName());
		if (state == null) {
			state = new ServiceStateImpl();
		}
		if (serviceLifecycException != null) {
			if (ServiceNotFoundException.class
					.isAssignableFrom(serviceLifecycException.getClass())) {
				state.addIDepend(new DependencyInfo(getState(),
						serviceLifecycException.getLocalizedMessage(),
						serviceLifecycException.getServiceName(), true));
			} else {
				state.addServiceState(new ServiceStateInfo(getState(),
						serviceLifecycException.getLocalizedMessage(),
						serviceLifecycException.getServiceName(), true));
			}
		} else {
			state.addServiceState(new ServiceStateInfo(getState(), "",
					getName(), false));
		}
		stateManager.setServiceState(getName(), state);
	}

	private void getListenerTasks(final ListenerNotification notification,
			final ArrayList<Runnable> tasks) {
		final IdentityHashMap<ServiceListener<? super S>, ServiceListener.Inheritance> listeners = this.listeners;
		for (ServiceListener<? super S> listener : listeners.keySet()) {
			tasks.add(new ListenerTask(listener, notification));
		}
	}

	void doExecute(final Runnable task) {
		assert !holdsLock(this);
		if (task == null)
			return;
		try {
			primaryRegistration.getContainer().getExecutor().execute(task);
		} catch (RejectedExecutionException e) {
			task.run();
		}
	}

	void doExecute(final ArrayList<Runnable> tasks) {
		assert !holdsLock(this);
		if (tasks == null)
			return;
		final Executor executor = primaryRegistration.getContainer()
				.getExecutor();
		for (Runnable task : tasks) {
			try {
				executor.execute(task);
			} catch (RejectedExecutionException e) {
				task.run();
			}
		}
	}

	public void setMode(final ServiceController.Mode newMode) {
		internalSetMode(null, newMode);
	}

	private boolean internalSetMode(final ServiceController.Mode expectedMode,
			final ServiceController.Mode newMode) {
		assert !holdsLock(this);
		if (newMode == null) {
			throw new IllegalArgumentException("newMode is null");
		}
		if (newMode != Mode.REMOVE
				&& primaryRegistration.getContainer().isShutdown()) {
			throw new IllegalArgumentException("Container is shutting down");
		}
		final ArrayList<Runnable> tasks = new ArrayList<Runnable>(4);
		synchronized (this) {
			final Mode oldMode = mode;
			if (expectedMode != null && expectedMode != oldMode) {
				return false;
			}
			if (oldMode == newMode) {
				return true;
			}
			internalSetMode(newMode, tasks);
			if (tasks.isEmpty()) {
				// if not empty, don't bother since transition should do nothing
				// until tasks are done
				transition(tasks);
			}
			asyncTasks += tasks.size();
		}
		doExecute(tasks);
		return true;
	}

	private void internalSetMode(final Mode newMode,
			final ArrayList<Runnable> taskList) {
		assert holdsLock(this);
		final ServiceController.Mode oldMode = mode;
		switch (oldMode) {
		case REMOVE: {
			if (state.compareTo(State.DOWN) >= 0) {
				throw new IllegalStateException("Service already removed");
			}
			getListenerTasks(ListenerNotification.REMOVE_REQUESTED, taskList);
			break;
		}
		case NEVER: {
			switch (newMode) {
			case REMOVE: {
				getListenerTasks(ListenerNotification.REMOVE_REQUESTED,
						taskList);
				break;
			}
			case ACTIVE: {
				// 可以不设置任何任务
			}
				break;
			}
			break;
		}
		case ON_DEMAND: {
			switch (newMode) {
			case REMOVE: {
				getListenerTasks(ListenerNotification.REMOVE_REQUESTED,
						taskList);
			}
			}
			break;
		}
		case ACTIVE: {
			switch (newMode) {
			case REMOVE:
				getListenerTasks(ListenerNotification.REMOVE_REQUESTED,
						taskList);
				switch (state) {
				case UP:
					command = new Command(Transition.UP_to_STOPPING);
					break;
				case PROBLEM:
					command = new Command(Transition.PROBLEM_to_STOPPING);
					break;
				}
			}
			break;
		}
		}
		mode = newMode;
	}

	public ServiceContainer getServiceContainer() {
		return primaryRegistration.getContainer();
	}

	public S getValue() throws IllegalStateException {
		return serviceValue.getValue().getValue();
	}

	public Service<S> getService() throws IllegalStateException {
		return serviceValue.getValue();
	}

	public ServiceName getName() {
		return primaryRegistration.getName();
	}

	private static final ServiceName[] NO_NAMES = new ServiceName[0];

	public ServiceName[] getAliases() {
		final ServiceRegistration[] aliasRegistrations = this.aliasRegistrations;
		final int len = aliasRegistrations.length;
		if (len == 0) {
			return NO_NAMES;
		}
		final ServiceName[] names = new ServiceName[len];
		for (int i = 0; i < len; i++) {
			names[i] = aliasRegistrations[i].getName();
		}
		return names;
	}

	public void addListener(final ServiceListener<? super S> listener) {
		assert !holdsLock(this);
		final State state;
		synchronized (this) {
			state = this.state;
			// Always run listener if removed.
			if (state != State.DOWN) {
				if (listeners.containsKey(listener)) {
					// Duplicates not allowed
					throw new IllegalArgumentException("Listener " + listener
							+ " already present on controller for "
							+ primaryRegistration.getName());
				}
				listeners.put(listener, ServiceListener.Inheritance.NONE);
				asyncTasks++;
			} else {
				asyncTasks += 2;
			}
		}
		invokeListener(listener, ListenerNotification.LISTENER_ADDED, null);
		if (state == State.DOWN) {
			logger.info("service " + primaryRegistration.getName()
					+ " is down.");
		}
	}

	public void addListener(final ServiceListener.Inheritance inheritance,
			final ServiceListener<Object> listener) {
		assert !holdsLock(this);
		final State state;
		synchronized (this) {
			state = this.state;
			// Always run listener if removed.
			if (state != State.DOWN) {
				if (listeners.containsKey(listener)) {
					// Duplicates not allowed
					throw new IllegalArgumentException("Listener " + listener
							+ " already present on controller for "
							+ primaryRegistration.getName());
				}
				listeners.put(listener, inheritance);
				asyncTasks++;
			} else {
				asyncTasks += 2;
			}
		}
		invokeListener(listener, ListenerNotification.LISTENER_ADDED, null);
		if (state == State.DOWN) {
			logger.info("service " + primaryRegistration.getName()
					+ " is down.");
		}
	}

	public void removeListener(final ServiceListener<? super S> listener) {
		synchronized (this) {
			listeners.remove(listener);
		}
	}

	public ServiceLifecycException getServiceLifecycException() {
		synchronized (this) {
			return serviceLifecycException;
		}
	}

	@Override
	public void retry() {
		assert !holdsLock(this);
		final ArrayList<Runnable> tasks;
		synchronized (this) {
			if (state.getState() != ServiceController.State.PROBLEM.getState()) {
				return;
			}
			serviceLifecycException = null;
			transition(tasks = new ArrayList<Runnable>());
			asyncTasks += tasks.size();
		}
		doExecute(tasks);
	}

	public ServiceController.Mode getMode() {
		synchronized (this) {
			return mode;
		}
	}

	public boolean compareAndSetMode(final Mode expectedMode, final Mode newMode) {
		if (expectedMode == null) {
			throw new IllegalArgumentException("expectedMode is null");
		}
		return internalSetMode(expectedMode, newMode);
	}

	public ServiceStatus getStatus() {
		synchronized (this) {

			final String name = primaryRegistration.getName()
					.getCanonicalName();
			final ServiceRegistration[] aliasRegistrations = this.aliasRegistrations;
			final int aliasLength = aliasRegistrations.length;
			final String[] aliases;
			if (aliasLength == 0) {
				aliases = NO_STRINGS;
			} else {
				aliases = new String[aliasLength];
				for (int i = 0; i < aliasLength; i++) {
					aliases[i] = aliasRegistrations[i].getName()
							.getCanonicalName();
				}
			}
			String serviceClass = "<unknown>";
			try {
				final Service<? extends S> value = serviceValue.getValue();
				if (value != null) {
					serviceClass = value.getClass().getName();
				}
			} catch (RuntimeException ignored) {
			}
			ServiceLifecycException startException = this.serviceLifecycException;
			return new ServiceStatus(name, aliases, serviceClass, mode.name(),
					state.getState(), state.name(),
					startException != null ? startException.toString() : null);
		}
	}

	IdentityHashMap<ServiceListener<? super S>, ServiceListener.Inheritance> getListeners() {
		return listeners;
	}

	private enum ListenerNotification {
		/** Notify the listener that is has been added. */
		LISTENER_ADDED,
		/** Notifications related to the current state. */
		TRANSITION,
		/** Notify the listener that an immediate dependency is unavailable. */
		SERVICE_UNAVAILABLE, SERVICE_AVAILABLE, TRANSITIVE_SERVICE_AVAILABLE, TRANSITIVE_SERVICE_UNAVAILABLE,
		/** Notify the listener that the service is going to be removed. */
		REMOVE_REQUESTED,
	}

	/**
	 * Invokes the listener, performing the notification specified.
	 * 
	 * @param listener
	 *            listener to be invoked
	 * @param notification
	 *            specified notification
	 * @param transition
	 *            the transition to be notified, only relevant if
	 *            {@code notification} is
	 *            {@link ListenerNotification#TRANSITION}
	 */
	private void invokeListener(final ServiceListener<? super S> listener,
			final ListenerNotification notification, final Transition transition) {
		assert !holdsLock(this);
		// first set the TCCL
		final ClassLoader contextClassLoader = setTCCL(listener.getClass()
				.getClassLoader());
		try {
			switch (notification) {
			case TRANSITION: {
				listener.transition(this, transition);
				break;
			}
			case LISTENER_ADDED: {
				listener.listenerAdded(this);
				break;
			}
			case SERVICE_UNAVAILABLE: {
				listener.serviceUnavailable(this);
				break;
			}
			case SERVICE_AVAILABLE: {
				listener.serviceAvailable(this);
				break;
			}
			case TRANSITIVE_SERVICE_UNAVAILABLE: {
				listener.transitiveServiceUnavailable(this);
				break;
			}
			case TRANSITIVE_SERVICE_AVAILABLE: {
				listener.transitiveServiceAvailable(this);
				break;
			}
			case REMOVE_REQUESTED: {
				listener.serviceRemoveRequested(this);
				break;
			}
			default:
				throw new IllegalStateException();
			}
		} catch (Throwable t) {
			// ServiceLogger.SERVICE.listenerFailed(t, listener);
		} finally {
			// reset TCCL
			setTCCL(contextClassLoader);
			// perform transition tasks
			final ArrayList<Runnable> tasks = new ArrayList<Runnable>();
			synchronized (this) {
				// Subtract one for this executing listener
				asyncTasks--;
				transition(tasks);
				asyncTasks += tasks.size();
			}
			doExecute(tasks);
		}
	}

	ServiceRegistration getPrimaryRegistration() {
		return primaryRegistration;
	}

	ServiceRegistration[] getAliasRegistrations() {
		return aliasRegistrations;
	}

	private static ClassLoader setTCCL(ClassLoader newTCCL) {
		final SecurityManager sm = System.getSecurityManager();
		final SetTCCLAction setTCCLAction = new SetTCCLAction(newTCCL);
		if (sm != null) {
			return AccessController.doPrivileged(setTCCLAction);
		} else {
			return setTCCLAction.run();
		}
	}

	enum ContextState {
		SYNC, ASYNC, COMPLETE, FAILED,
	}

	@Override
	public String toString() {
		return String.format("Controller for %s@%x", getName(),
				Integer.valueOf(hashCode()));
	}

	private class ServiceUnavailableTask implements Runnable {

		public void run() {
			try {
				assert !holdsLock(ServiceControllerImpl.this);
				final ServiceName serviceName = primaryRegistration.getName();
				try {
					final Service<? extends S> service = serviceValue
							.getValue();
					if (service == null) {
						throw new IllegalArgumentException("Service is null");
					}
					waitingService(service, true);
				} catch (WaitingException e) {
					e.setServiceName(serviceName);
					logger.error(e.getMessage(), e);
					serviceLifecycException = e;
				} catch (Throwable t) {
					WaitingException e = new WaitingException(
							"Failed to set service to waiting", t, serviceName);
					logger.error(e.getMessage(), e);
					serviceLifecycException = e;
				}
				final ArrayList<Runnable> tasks = new ArrayList<Runnable>();
				synchronized (ServiceControllerImpl.this) {
					asyncTasks--;
					transition(tasks);
					asyncTasks += tasks.size();
				}
				doExecute(tasks);
			} catch (Throwable t) {
				logger.error(t.getMessage(), t);
			}
		}

	}

	private class ServiceAvailableTask implements Runnable {

		public void run() {
			try {
				assert !holdsLock(ServiceControllerImpl.this);
				final ServiceName serviceName = primaryRegistration.getName();
				try {
					final Service<? extends S> service = serviceValue
							.getValue();
					if (service == null) {
						throw new IllegalArgumentException("Service is null");
					}
					waitingService(service, false);
				} catch (WaitingException e) {
					e.setServiceName(serviceName);
					logger.error(e.getMessage(), e);
				} catch (Throwable t) {
					WaitingException e = new WaitingException(
							"Failed to set service to up", t, serviceName);
					logger.error(e.getMessage(), e);
					serviceLifecycException = e;
				}
				final ArrayList<Runnable> tasks = new ArrayList<Runnable>();
				synchronized (ServiceControllerImpl.this) {
					asyncTasks--;
					transition(tasks);
					asyncTasks += tasks.size();
				}
				doExecute(tasks);
			} catch (Throwable t) {
				logger.error(t.getMessage(), t);
			}
		}
	}

	private void waitingService(Service<? extends S> service, boolean enable)
			throws WaitingException {
		final ClassLoader contextClassLoader = setTCCL(service.getClass()
				.getClassLoader());
		try {
			service.waiting(enable);
		} finally {
			setTCCL(contextClassLoader);
		}
	}

	private class StartTask implements Runnable {

		public void run() {
			assert !holdsLock(ServiceControllerImpl.this);
			final ServiceName serviceName = primaryRegistration.getName();
			final long startNanos = System.nanoTime();
			final StartContextImpl context = new StartContextImpl(startNanos);
			try {
				final Service<? extends S> service = serviceValue.getValue();
				if (service == null) {
					throw new IllegalArgumentException("Service is null");
				}
				startService(service, context);
				final ArrayList<Runnable> tasks = new ArrayList<Runnable>();
				synchronized (ServiceControllerImpl.this) {
					if (context.state != ContextState.SYNC) {
						return;
					}
					context.state = ContextState.COMPLETE;
					if (ServiceContainerImpl.PROFILE_OUTPUT != null) {
						writeProfileInfo('S', startNanos, System.nanoTime());
					}
					// Subtract one for this task
					asyncTasks--;
					transition(tasks);
					asyncTasks += tasks.size();
				}
				doExecute(tasks);
			} catch (ServiceNotFoundException e) {
				e.setServiceName(serviceName);
				startFailed(e, serviceName, context, startNanos);
			} catch (StartException e) {
				e.setServiceName(serviceName);
				startFailed(e, serviceName, context, startNanos);
			} catch (Throwable t) {
				t.printStackTrace();
				StartException e = new StartException(
						"Failed to start service", t, serviceName);
				startFailed(e, serviceName, context, startNanos);
			}
		}

		private void startService(Service<? extends S> service,
				StartContext context) throws StartException,
				ServiceNotFoundException {
			final ClassLoader contextClassLoader = setTCCL(service.getClass()
					.getClassLoader());
			try {
				service.start(context);
			} finally {
				setTCCL(contextClassLoader);
			}
		}

		private void startFailed(ServiceLifecycException e,
				ServiceName serviceName, StartContextImpl context,
				long startNanos) {
			// ServiceLogger.FAIL.startFailed(e, serviceName);
			final ArrayList<Runnable> tasks;
			synchronized (ServiceControllerImpl.this) {
				final ContextState oldState = context.state;
				if (oldState != ContextState.SYNC
						&& oldState != ContextState.ASYNC) {
					// ServiceLogger.FAIL.exceptionAfterComplete(e,
					// serviceName);
					return;
				}
				context.state = ContextState.FAILED;
				serviceLifecycException = e;
				if (ServiceContainerImpl.PROFILE_OUTPUT != null) {
					writeProfileInfo('F', startNanos, System.nanoTime());
				}
				asyncTasks--;
				transition(tasks = new ArrayList<Runnable>());
				asyncTasks += tasks.size();
			}
			doExecute(tasks);
		}
	}

	private class StopTask implements Runnable {

		public void run() {
			assert !holdsLock(ServiceControllerImpl.this);
			final long startNanos = System.nanoTime();
			final StopContextImpl context = new StopContextImpl(startNanos);
			boolean ok = false;
			try {
				try {
					final Service<? extends S> service = serviceValue
							.getValue();
					if (service != null) {
						stopService(service, context);
						ok = true;
					} else {
						// ServiceLogger.ROOT.stopServiceMissing(serviceName);
					}
				} catch (Throwable t) {
					// ServiceLogger.FAIL.stopFailed(t, serviceName);
				}
			} finally {
				final ArrayList<Runnable> tasks;
				synchronized (ServiceControllerImpl.this) {
					if (ok && context.state != ContextState.SYNC) {
						// We want to discard the exception anyway, if there was
						// one. Which there can't be.
						// noinspection ReturnInsideFinallyBlock
						return;
					}
					context.state = ContextState.COMPLETE;
				}
				synchronized (ServiceControllerImpl.this) {
					if (ServiceContainerImpl.PROFILE_OUTPUT != null) {
						writeProfileInfo('X', startNanos, System.nanoTime());
					}
					// Subtract one for this task
					asyncTasks--;
					transition(tasks = new ArrayList<Runnable>());
					asyncTasks += tasks.size();
				}
				doExecute(tasks);
			}
		}

		private void stopService(Service<? extends S> service,
				StopContext context) throws StopException {
			final ClassLoader contextClassLoader = setTCCL(service.getClass()
					.getClassLoader());
			try {
				service.stop(context);
			} finally {
				setTCCL(contextClassLoader);
			}
		}

	}

	private class ListenerTask implements Runnable {

		private final ListenerNotification notification;
		private final ServiceListener<? super S> listener;
		private final Transition transition;

		ListenerTask(final ServiceListener<? super S> listener,
				final Transition transition) {
			this.listener = listener;
			this.transition = transition;
			notification = ListenerNotification.TRANSITION;
		}

		ListenerTask(final ServiceListener<? super S> listener,
				final ListenerNotification notification) {
			this.listener = listener;
			transition = null;
			this.notification = notification;
		}

		public void run() {
			assert !holdsLock(ServiceControllerImpl.this);
			if (ServiceContainerImpl.PROFILE_OUTPUT != null) {
				final long start = System.nanoTime();
				try {
					invokeListener(listener, notification, transition);
				} finally {
					writeProfileInfo('L', start, System.nanoTime());
				}
			} else {
				invokeListener(listener, notification, transition);
			}
		}
	}

	private class RemoveTask implements Runnable {

		public void run() {
			try {
				assert getMode() == ServiceController.Mode.REMOVE;
				primaryRegistration.clearInstance(ServiceControllerImpl.this);
				for (ServiceRegistration registration : aliasRegistrations) {
					registration.clearInstance(ServiceControllerImpl.this);
				}
				final ArrayList<Runnable> tasks = new ArrayList<Runnable>();
				synchronized (ServiceControllerImpl.this) {
					// Subtract one for this task
					asyncTasks--;
					transition(tasks);
					asyncTasks += tasks.size();
				}
				doExecute(tasks);
			} catch (Throwable t) {
				logger.error(t.getMessage(), t);
			}
		}
	}

	private class StartContextImpl implements StartContext {

		private ContextState state = ContextState.SYNC;

		private final long startNanos;

		private StartContextImpl(final long startNanos) {
			this.startNanos = startNanos;
		}

		public void failed(StartException reason) throws IllegalStateException {
			final ArrayList<Runnable> tasks = new ArrayList<Runnable>();
			synchronized (ServiceControllerImpl.this) {
				if (state != ContextState.ASYNC) {
					throw new IllegalStateException(ILLEGAL_CONTROLLER_STATE);
				}
				if (reason == null) {
					reason = new StartException(
							"Start failed, and additionally, a null cause was supplied");
				}
				state = ContextState.FAILED;
				final ServiceName serviceName = getName();
				reason.setServiceName(serviceName);
				// ServiceLogger.FAIL.startFailed(reason, serviceName);
				serviceLifecycException = reason;
				if (ServiceContainerImpl.PROFILE_OUTPUT != null) {
					writeProfileInfo('F', startNanos, System.nanoTime());
				}
				// Subtract one for this task
				asyncTasks--;
				transition(tasks);
				asyncTasks += tasks.size();
			}
			doExecute(tasks);
		}

		public void asynchronous() throws IllegalStateException {
			synchronized (ServiceControllerImpl.this) {
				if (state == ContextState.SYNC) {
					state = ContextState.ASYNC;
				} else {
					throw new IllegalStateException(ILLEGAL_CONTROLLER_STATE);
				}
			}
		}

		public void complete() throws IllegalStateException {
			final ArrayList<Runnable> tasks = new ArrayList<Runnable>();
			synchronized (ServiceControllerImpl.this) {
				if (state != ContextState.ASYNC) {
					throw new IllegalStateException(ILLEGAL_CONTROLLER_STATE);
				} else {
					state = ContextState.COMPLETE;
					if (ServiceContainerImpl.PROFILE_OUTPUT != null) {
						writeProfileInfo('S', startNanos, System.nanoTime());
					}
					// Subtract one for this task
					asyncTasks--;
					transition(tasks);
					asyncTasks += tasks.size();
				}
			}
			doExecute(tasks);
		}

		public long getElapsedTime() {
			return System.nanoTime() - lifecycleTime;
		}

		public ServiceController<?> getController() {
			return ServiceControllerImpl.this;
		}

		public void execute(final Runnable command) {
			final ClassLoader contextClassLoader = setTCCL(command.getClass()
					.getClassLoader());
			try {
				command.run();
			} finally {
				setTCCL(contextClassLoader);
			}
		}
	}

	private void writeProfileInfo(final char statusChar, final long startNanos,
			final long endNanos) {
		final ServiceRegistration primaryRegistration = this.primaryRegistration;
		final ServiceName name = primaryRegistration.getName();
		final ServiceContainer container = primaryRegistration.getContainer();
		final Writer profileOutput = container.getProfileOutput();
		if (profileOutput != null) {
			synchronized (profileOutput) {
				try {
					final long startOffset = startNanos - container.getStart();
					final long duration = endNanos - startNanos;
					profileOutput.write(String.format("%s\t%s\t%d\t%d\n",
							name.getCanonicalName(),
							Character.valueOf(statusChar),
							Long.valueOf(startOffset), Long.valueOf(duration)));
				} catch (IOException e) {
					// ignore
				}
			}
		}
	}

	private class StopContextImpl implements StopContext {

		private ContextState state = ContextState.SYNC;

		private final long startNanos;

		private StopContextImpl(final long startNanos) {
			this.startNanos = startNanos;
		}

		public void asynchronous() throws IllegalStateException {
			synchronized (ServiceControllerImpl.this) {
				if (state == ContextState.SYNC) {
					state = ContextState.ASYNC;
				} else {
					throw new IllegalStateException(ILLEGAL_CONTROLLER_STATE);
				}
			}
		}

		public void complete() throws IllegalStateException {
			synchronized (ServiceControllerImpl.this) {
				if (state != ContextState.ASYNC) {
					throw new IllegalStateException(ILLEGAL_CONTROLLER_STATE);
				}
				state = ContextState.COMPLETE;
			}
			final ArrayList<Runnable> tasks = new ArrayList<Runnable>();
			synchronized (ServiceControllerImpl.this) {
				if (ServiceContainerImpl.PROFILE_OUTPUT != null) {
					writeProfileInfo('X', startNanos, System.nanoTime());
				}
				// Subtract one for this task
				asyncTasks--;
				transition(tasks);
				asyncTasks += tasks.size();
			}
			doExecute(tasks);
		}

		public ServiceController<?> getController() {
			return ServiceControllerImpl.this;
		}

		public void execute(final Runnable command) {
			final ClassLoader contextClassLoader = setTCCL(command.getClass()
					.getClassLoader());
			try {
				command.run();
			} finally {
				setTCCL(contextClassLoader);
			}
		}

		public long getElapsedTime() {
			return System.nanoTime() - lifecycleTime;
		}
	}

	@Override
	public org.dding.msc.service.ServiceController.State getState() {
		return state;
	}

	@Override
	public Command getCommand() {
		return command;
	}
}
